{{ define "SlotViz" }}
  <script src="/js/d3.min.js"></script>
  <script>
    var currentEpoch = {{ .CurrentEpoch }}
    var genesisTimeStamp = {{ .ChainGenesisTimestamp }}
    var secondsPerSlot = {{ .ChainSecondsPerSlot }}
    var slotsPerEpoch = {{ .ChainSlotsPerEpoch }}
    const data = {{ .Data.SlotVizData }}
    const epochs = data.Epochs
    const hardforkEpoch = data.Config?.HardforkEpoch?.Value ? parseInt(data.Config.HardforkEpoch.Value) : 0
    const hardforkName = data.Config?.HardforkName?.Value || ''
    const selector = `#${data.Selector}`
    var vizChart

    function setupChart() {
      if (vizChart) {
        vizChart.parentNode.innerHTML = ""
        vizChart = null
      }
      const width = $(selector)?.width() || 0
      /// Chat Dimensions
      const margin = { top: 20, right: 0, bottom: 0, left: 30 }
      let height = 120 + margin.top + margin.bottom + (epochs?.length || 5) * 50

      var roma = ["#343a4033", "#ff3333", "#cc3333", "#33cc33"]

      // define a color interpolation that turnes green over 66%
      var color = d3.scaleSequential().domain([0, 99]).interpolator(d3.interpolateDiscrete(roma))

      var colorProposed = d3.scaleSequential().domain([0, 12]).interpolator(d3.interpolateRgb("rgba(100, 200, 100, 1)", "rgba(100, 200, 100, 0.3)"))
      // Chart Svg
      var svg = d3.select(selector).append("svg").attr("viewBox", [0, 0, width, height]).attr("style", `max-width: ${width}px; font: 10px sans-serif;`)

      // Slot rectangle size is relative to width so it scales on mobile
      var rectSize = (width - margin.right - margin.left) / 42

      // Mobile-only: tighten vertical spacing between each rectangles+bar combination to ~10px gap
      if (width < 576) {
        var desiredRowGap = 0
        // rowStep approximates: slotRect + gap between rect and bar + bar height + desired gap between combos
        var rowStep = Math.round(rectSize + 4 + 0 + desiredRowGap) // 4 = barGap, 11 = barHeight
        height = 120 + margin.top + margin.bottom + (epochs?.length || 5) * rowStep
        // Update the svg viewBox height so the chart compacts vertically on mobile
        svg.attr("viewBox", [0, 0, width, height])
      }

      // Define X axis
      var x = d3
        .scaleLinear()
        .domain([0, slotsPerEpoch])
        .range([margin.left, width - margin.right])
        .interpolate(d3.interpolateRound)

      var participationX = d3
        .scaleLinear()
        .domain([0, 100])
        .range([margin.left, width - margin.right])
        .interpolate(d3.interpolateRound)

      // define Y axis
      var y = d3
        .scaleBand()
        .domain(epochs.map((d) => d.epoch))
        .range([margin.top, height - margin.bottom])
        .padding(0.1)

      svg
        .append("g")
        .attr("transform", `translate(0,${margin.top})`)
        .call(d3.axisTop(x))
        .call((g) => g.select(".domain").remove())

      var gy = svg
        .append("g")
        .attr("transform", `translate(${margin.left}, 0)`)
        .call(d3.axisLeft(y))
        .call((g) => g.selectAll("line").remove())
        .call((g) => g.select(".domain").remove())

      function createBars(svg) {
        return svg.append("g")
      }

      function createLabels(svg) {
        return svg.append("g").attr("text-anchor", "end").attr("transform", `translate(6, 5)`)
      }

      function createSlots(svg) {
        return svg.append("g").attr("text-anchor", "middle")
      }

      var slots = createSlots(svg)
      var bars = createBars(svg)
      var labels = createLabels(svg)

      // Layout constants (responsive)
      // Bar height stays constant for readability; spacing adapts with rect size
      var barHeight = 15
      // Gap between the bottom of the slot squares row and the top of the participation bar
      var barGap = 4
      // Distance from the vertical center of a band to the top of the participation bar
      var barYOffset = rectSize / 2 + barGap
      // Offset to reach the visual middle of the bar for text placement
      var labelInsideBarOffset = barHeight / 2
      // Global nudge to counter baseline differences across browsers (negative moves text up)
      var labelNudge = -4

      vizChart = Object.assign(svg.node(), {
        update(newData) {
          $(function () {
            $('[data-toggle="tooltip"]').tooltip()
          })
          var t = svg.transition().duration(550)
          // update y axis domain
          y.domain(newData.map((d) => d.epoch))

          // update y axis labels
          gy.join("g")
            .call(d3.axisLeft(y))
            .call((g) => g.selectAll("line").remove())
            .call((g) => g.select(".domain").remove())

          labels
            .selectAll("text")
            .data(newData, (d) => {
              return d.epoch
            })
            .join(
              (enter) => {
                return enter
                  .append("text")
                  .attr("y", (d) => y(d.epoch) + y.bandwidth() / 2 + barYOffset + labelInsideBarOffset + labelNudge)
                  .attr("x", (d) => {
                    if (d.participation === 0) {
                      return participationX(99) - participationX(0)
                    }
                    return participationX(d.participation) - x(0)
                  })
                  .attr("style", "font-size: .7rem; z-index: 99; dominant-baseline: middle; alignment-baseline: middle;")
                  .text((d) => {
                    if (d.participation) {
                      var txt = d.finalized ? "Finalized" : d.justified ? "Justified" :"Justifying"
                      return txt + " " + d.participation + "%"
                    } else {
                      return d.slots[0].Slot ? "Processing..." : ""
                    }
                  })
              },
              (update) => {
                return update

                  .text((d) => {
                    if (d.participation) {
                      var txt = d.finalized ? "Finalized" : d.justified ? "Justified" : "Justifying"
                      return txt + " " + d.participation + "%"
                    } else {
                      return d.slots[0].Slot ? "Processing..." : ""
                    }
                  })
                  .call((label) => {
                    return label
                      .transition(t)
                      .attr("y", (d) => y(d.epoch) + y.bandwidth() / 2 + barYOffset + labelInsideBarOffset + labelNudge)
                      .attr("fill", (d) => {
                        if (d.participation === 0) {
                          return "black"
                        }
                        return `${d3.lab(color(d.participation)).l < 60 ? "white" : "black"}`
                      })
                      .attr("x", (d) => {
                        if (d.participation === 0) {
                          return participationX(99) - participationX(0)
                        }
                        return participationX(d.participation) - x(0)
                      })
                  })
              },
              (exit) => {
                return exit.remove()
              }
            )

          slots
            .selectAll("g")
            .data(newData, (d) => d.epoch)
            .join(
              (enter) => {
                return enter.append("g").attr("transform", ({ epoch }) => `translate(0, ${y(epoch)})`)
              },
              (update) => {
                return update.call((slotRow) => {
                  return slotRow.transition(t).attr("transform", ({ epoch }) => `translate(0, ${y(epoch)})`)
                })
              },
              (exit) => {
                return exit.remove()
              }
            )
            .selectAll("rect")
            .data((d) => d.slots)
            .join(
              (enter) => {
                return (
                  enter
                    .append("rect")
                    .attr("status", (d) => d.status)
                    .attr("x", (d, i) => x(i))
                    .attr("y", (d, i) => y.bandwidth() / 2 - rectSize / 2)
                    .attr("slot", (d) => d.Slot)
                    .on("mouseup", (d) => {
                      var slot = d.target.getAttribute("slot")
                      if (d.which == 2 ){
                        window.open(`/slot/${slot}`,"_blank");
                      } else if (d.which == 1){
                        window.location = "/slot/" + slot
                      }
                    })
                    .attr("data-original-title", (s, i) => {
                      if (!s.Slot) {
                        return
                      }
                      if (s.active) {
                        return `Current Slot ${s.Slot}`
                      }
                      if (s.status === "proposed" && s.delay) {
                        return `Slot: ${s.Slot} Proposed in ${s.delay}s`
                      } else if (s.status === "proposed") {
                        return `Slot: ${s.Slot} Status: ${s.status}`
                      }
                      if (s.status !== "scheduled") {
                        if (s.status === "scheduled-missed") {
                          return `Slot: ${s.Slot} Status: Scheduled`
                        }
                        return `Slot: ${s.Slot} Status: ${s.status}`
                      } else {
                        return `Slot: ${s.Slot}`
                      }
                    })
                    // .attr("data-toggle", (s, i) => s.status !== "scheduled" || s.active ? "tooltip" : "")
                    .attr("data-toggle", (s, i) => "tooltip")
                    .attr("stroke-width", ".5")
                    .attr("stroke", (d) => (d.active ? "var(--body-color)" : "transparent"))
                    .attr("width", (d) => rectSize)
                    .attr("height", (d) => rectSize)
                    .attr("rx", "1")
                    .attr("ry", "1")
                    .attr("fill", (d) => {
                      if (d.status === "missed") {
                        return "rgba(200,100,100,0.8)"
                      } else if (d.status === "proposed") {
                        if (d.delay) {
                          return colorProposed(d.delay)
                        } else {
                          return colorProposed(1)
                        }
                      } else if (d.status === "scheduled-missed") {
                        return "var(--highlight)"
                      } else if (d.status === "orphaned") {
                        return "rgba(100,100, 200, 0.8)"
                      } else if (d.status === "genesis") {
                        return "rgba(119, 110, 255, 0.8)"
                      } else {
                        return "rgba(100,100,100, 0.5)"
                      }
                    })
                )
              },
              (update) => {
                return update
                  .attr("stroke", (d) => (d.active ? "var(--body-color)" : "transparent"))
                  .attr("status", (d) => d.status)
                  .attr("fill", (d) => {
                    if (d.status === "missed") {
                      return "rgba(200,100,100,0.8)"
                    } else if (d.status === "proposed") {
                      if (d.delay) {
                        return colorProposed(d.delay)
                      } else {
                        return colorProposed(1)
                      }
                    } else if (d.status === "scheduled-missed") {
                      return "var(--highlight)"
                    } else if (d.status === "orphaned") {
                      return "rgba(100,100, 200, 0.8)"
                    } else if (d.status === "genesis") {
                      return "rgba(119, 110, 255, 0.8)"
                    } else {
                      return "rgba(100,100,100, 0.5)"
                    }
                  })
                  .attr("class", (s, i) => {
                    if (s.active) {
                      return "active-slot"
                    } else {
                      return ""
                    }
                  })
                  .attr("data-original-title", (s, i) => {
                    if (!s.Slot) {
                      return
                    }
                    if (s.active) {
                      return `Current Slot ${s.Slot}`
                    }
                    if (s.status === "proposed" && s.delay) {
                      return `Slot: ${s.Slot} Proposed in ${s.delay}s`
                    } else if (s.status === "proposed") {
                      return `Slot: ${s.Slot} Status: ${s.status}`
                    }
                    if (s.status !== "scheduled") {
                      if (s.status === "scheduled-missed") {
                        return `Slot: ${s.Slot} Status: scheduled`
                      }
                      return `Slot: ${s.Slot} Status: ${s.status}`
                    } else {
                      return `Slot: ${s.Slot}`
                    }
                  })
              },
              (exit) => exit.remove()
            )
          // bars
          bars
            .selectAll("rect")
            .data(newData, (d) => {
              return d.epoch
            })
            .join(
              (enter) => {
                enter
                  .append("rect")
                  .attr("id", ({ epoch }) => epoch)
                  .attr("fill", ({ participation, epoch }) => {
                    if (!participation) {
                      return "var(--shadow-light)"
                    }
                    return color(participation)
                  })
                  .attr("y", ({ epoch }) => y(epoch) + y.bandwidth() / 2 + barYOffset)
                  .attr("x", x(0))
                  .attr("width", ({ participation }) => {
                    if (!participation) {
                      return participationX(99.4) - participationX(0)
                    }
                    return participationX(participation) - participationX(0)
                  })
                  .attr("height", barHeight)
                  .attr("data-original-title", (d, i) => `Epoch ${d.epoch} Participation ${d.participation}%`)
                  .attr("data-toggle", "tooltip")
                  .attr("epoch", (d) => d.epoch)
                  .on("mouseup", (d) => {
                    var epoch = d.target.getAttribute("epoch")
                    if (d.which == 2 ){
                      window.open(`/epoch/${epoch}`,"_blank");
                    } else if (d.which == 1){
                      window.location = "/epoch/" + epoch
                    }
                  })
              },
              (update) => {
                return (
                  update
                    .attr("data-original-title", (d, i) => `Epoch ${d.epoch} Participation ${d.participation}%`)
                    // .attr("class", ({ participation }) => {
                    //       if (participation === 0) {
                    //         return "active-epoch"
                    //       } else {
                    //         return ""
                    //       }
                    //   })
                    .call((bar) =>
                      bar
                        .transition(t)
                        .attr("y", ({ epoch }) => y(epoch) + y.bandwidth() / 2 + barYOffset)
                        .attr("width", ({ participation }) => {
                          if (participation === 0) {
                            return participationX(99.4) - participationX(0)
                          }
                          return participationX(participation) - participationX(0)
                        })
                        .attr("fill", ({ participation }) => {
                          if (participation === 0) {
                            return "var(--shadow-light)"
                          }
                          return color(participation)
                        })
                    )
                )
              },
              (exit) => {
                return exit.remove()
              }
            )
        },
      })
      addWaterMark()
    }

    var lastSlotVizData;

    function fetchMetrics() {
      fetch("/launchMetrics")
        .then((res) => res.json())
        .then((data) => {
          lastSlotVizData = data
          processLaunchMetrics(data)
        })
        .catch((error) => {
          console.error("error fetching resource", error)
        })
    }

    function processLaunchMetrics(data) {
      data = data || []
      if (hardforkEpoch > 0) {
        data = data.filter((d) => d.epoch >= hardforkEpoch)
      }
      for (var i = 0; i < data.length; i++) {
        if (data[i].participation <= 1 && data[i].participation > 0) {
          data[i].participation = Math.round(data[i].participation * 10000) / 100
        }
      }
      while (data.length < 5 && hardforkEpoch) {
        epoch = data.length ? data[0].epoch + 1 : hardforkEpoch
        data.splice(0, 0, {
          epoch,
          finalized: false,
          justified: false,
          justifying: false,
          participation: 0,
          slots: Array(slotsPerEpoch).fill({}),
        })
      }
      vizChart?.update(data)
    }

    let slotVizCountdown
    function updateCountdown() {
      const hfTs = (genesisTimeStamp + hardforkEpoch * slotsPerEpoch * secondsPerSlot) * 1000
      const now = Date.now()
      if (hfTs > now) {
        if (!slotVizCountdown) {
          slotVizCountdown = document.createElement("div")
          slotVizCountdown.className = "mb-3"
          $(selector).prepend(slotVizCountdown)
        }
        const slots = Math.ceil((hfTs - now) / 1000 / secondsPerSlot)
        slotVizCountdown.innerHTML = `<b>${hardforkName} Hardfork</b> ${getRelativeTime(luxon.DateTime.fromMillis(hfTs))} | ${slots} ${slots > 1 ? 'slots' : 'slot'}`
        setTimeout(updateCountdown, 1000)
      } else {
        if (slotVizCountdown) {
          $(slotVizCountdown).remove()
          slotVizCountdown = null
          fetchMetrics()
        }
      }
    }

    window.addEventListener("DOMContentLoaded", function () {
      if (hardforkEpoch && hardforkEpoch > currentEpoch) {
        updateCountdown()
      }
      setupChart()
      processLaunchMetrics(epochs)
      setInterval(fetchMetrics, 3000)
    })

    window.addEventListener("resize", function (){
      setupChart()
      processLaunchMetrics(lastSlotVizData);
    })

    function addWaterMark() {
      const watermarkContainer = document.createElement("div")
      watermarkContainer.style.position = "relative"
      watermarkContainer.classList.add("p-2")

      const watermarkContent = document.createElement("a")
      watermarkContent.href = "https://beaconcha.in"
      watermarkContent.style.position = "absolute"
      watermarkContent.style.top = "100%"
      watermarkContent.style.right = 0
      watermarkContent.innerText = "beaconcha.in"
      watermarkContent.classList.add("text-muted")
      watermarkContent.classList.add("small")

      watermarkContainer.appendChild(watermarkContent)

      $(selector).append(watermarkContainer)
    }
  </script>
{{ end }}
